/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
/*
 * main.c
 * Copyright (C) Maxim Ermilov 2009 <zaspire@rambler.ru>
 * 
 * main.c is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * main.c is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <assert.h>
#include <glib.h>
#include <jsapi.h>
#include <jsobj.h>
#include <jsscan.h>
#include <jsparse.h>
#include <jsarena.h>
#include <jsemit.h>
#include <jsinterp.h>
#include <jsatom.h>
#include <jsfun.h>
#include <jscntxt.h>

#define GC_MAX_MEM (128*1024*1024)
#define JS_STACK_SIZE (8192)

static JSClass global_class = {
    "global", JSCLASS_GLOBAL_FLAGS,
    JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
    JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
    JSCLASS_NO_OPTIONAL_MEMBERS
};

int CN = 0;

void printDesc (JSContext *context, JSObject  *parent, jsid id, char * prefix)
{
CN++;
	jsval n;

	JS_IdToValue (context, id, &n);

	printf ("%s %s ", prefix, JS_GetTypeName (context, JS_TypeOfValue (context, n)));

	JSString *str = JS_ValueToString(context,n);
	puts (g_utf16_to_utf8 (JS_GetStringChars (str), -1, NULL, NULL, NULL));

	JSObject *obj;
	if (!JS_GetProperty(context, parent, g_utf16_to_utf8 (JS_GetStringChars (str), -1, NULL, NULL, NULL), &n))
	{
		g_error ("Can not get property");
	}

	if (JSVAL_IS_OBJECT(n))
	{
		printf ("%s \t %s \n", prefix, JS_GetTypeName (context, JS_TypeOfValue (context, n)));
		JS_ValueToObject (context, n, &obj);

		JSObject *parent = JS_GetPrototype (context, obj);
		if (parent && CN == 1)
		{
			gchar *pref = g_strconcat (prefix, "\t", NULL);
			printf ("%s \t %s \n", prefix, "parent");
			JSIdArray *oidb = JS_Enumerate (context, parent);
			if (oidb == NULL)
			{
				g_error ("Can not enumerate object");
			}

			jsint k;
			for (k = 0; k < oidb->length; k++)
			{
				gchar *pref2 = g_strconcat (pref, "\t", NULL);
				printDesc (context, JS_GetGlobalObject(context), oidb->vector[k], pref2);
				g_free (pref2);
			}
			g_free (pref);
		}

		JSIdArray *oida = JS_Enumerate (context, obj);
		if (oida == NULL)
		{
			g_error ("Can not enumerate object");
		}

		jsint k;
		for (k = 0; k < oida->length; k++)
		{
			gchar *pref = g_strconcat (prefix, "\t", NULL);
			printDesc (context, obj, oida->vector[k], pref);
			g_free (pref);
		}
	}
	else
	{
		printf ("%s \t %s ", prefix, JS_GetTypeName (context, JS_TypeOfValue (context, n)));
		JSString *str = JS_ValueToString(context,n);
		printf ("\t");
		puts (g_utf16_to_utf8 (JS_GetStringChars (str), -1, NULL, NULL, NULL));
	}
CN--;
}

JSContext *context;


char * dummyCodeCut (char* str)
{
	int i, k;
	int len = strlen (str);
	for (i = 3; i < len; i++)
	{
		if (str[i] == 'r' && str[i - 1] == 'o' && str[i - 2] == 'f') {
			i -= 2;
			k = 0;
			for (; i < len; i++) {
				if (str[i] == '{') k++;
				if (str[i] == '}')
				{
					str[i] = ' ';
					if (k)
						k--;
					else
						break;
				}
				str[i] = ' ';
			}
		}
	}
	return str;
}

JSParseNode* print (JSParseNode *node, char *pref, char **code)
{
	char *pr = g_strconcat(pref, "\t", NULL);
	JSParseNode *iter;
	gchar *in;
	*code = g_strdup ("");
if (node == NULL)
	return node;
	printf ("%s%d\n",pref,node->pn_type);
	switch ((JSParseNodeArity)node->pn_arity)
	{
		case PN_UNARY:
			printf ("%sUNARY\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
			case TOK_SEMI:
				print (node->pn_u.unary.kid, pr, code);
				*code = g_strconcat (*code, ";", NULL);
				break;
			}
			break;
		case PN_LIST:
			printf ("%sLIST\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
			case TOK_LP:
				print (node->pn_u.list.head, pr, code);
				*code = g_strconcat (*code, "(", NULL);
				int k = 0;
				for (iter = node->pn_u.list.head->pn_next; iter != NULL; iter = iter->pn_next, k++)
				{
					print (iter, pr, &in);
					if (k)
						*code = g_strconcat (*code, ", ", in, NULL);
					else
						*code = g_strconcat (*code, in, NULL);
				}

				*code = g_strconcat (*code, ")", NULL);
				break;
			case TOK_LC:
				*code = g_strdup ("{");
				for (iter = node->pn_u.list.head; iter != NULL; iter = iter->pn_next)
				{
					print (iter, pr, &in);
					*code = g_strconcat (*code, in, "\n", NULL);				
				}
				*code = g_strconcat (*code, "}", NULL);
				break;
			case TOK_NEW:
				///NOT COMPLETE
				print (node->pn_u.list.head, pr, code);
				*code = g_strconcat (*code, " ( ", NULL);
				k = 0;
				for (iter = node->pn_u.list.head->pn_next; iter != NULL; iter = iter->pn_next)
				{
					print (iter, pr, &in);
					if (k)
						*code = g_strconcat (*code,", ", in, NULL);
					else
						*code = g_strconcat (*code, in, NULL);
					k++;
				}
				*code = g_strconcat (*code, " )", NULL);
				break;
			}
			break;
		case PN_BINARY:
			printf ("%sBINARY\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
				case TOK_ASSIGN:
					///NOT COMPLETE
					print (node->pn_u.binary.left, pr, code);
					print (node->pn_u.binary.right, pr, &in);

					*code = g_strconcat (*code, " = ", in, NULL);
					
					break;
			}
			break;
		case PN_FUNC:
			printf ("%sFUNC\n", pref);

			JSObject * object = ATOM_TO_OBJECT(node->pn_funAtom);
			JSFunction * function = (JSFunction *) JS_GetPrivate(context, object);

			*code = g_strconcat (*code, " function ", NULL);

			if (function->atom) {
				*code = g_strconcat (*code, js_AtomToPrintableString(context, function->atom), NULL);
			}

			*code = g_strconcat (*code, " (", NULL);
			/* function parameters */
			JSAtom ** params = malloc(function->nargs * sizeof(JSAtom *));
			int i;
			for (i = 0; i < function->nargs; i++) {
			    /* initialize to NULL for sanity check */
			    params[i] = NULL;
			}
			JSScope * scope = OBJ_SCOPE(object);
			JSScopeProperty * scope_property;
			for (scope_property = SCOPE_LAST_PROP(scope);
			        scope_property != NULL;
			        scope_property = scope_property->parent) {
			    if (!JSID_IS_ATOM (scope_property->id) || ((uint16)scope_property->shortid) >= function->nargs) {
			    continue;
			    }
			    params[(uint16) scope_property->shortid] = JSID_TO_ATOM(scope_property->id);
			}
			int k = 0;
			for (i = 0; i < function->nargs; i++) {
			    assert(params[i] != NULL);
				if (!k)
					*code = g_strconcat (*code, js_AtomToPrintableString(context, params[i]), NULL);
				else
					*code = g_strconcat (*code, ", ", js_AtomToPrintableString(context, params[i]), NULL);
				k++;
		      /* params[i] is a JSAtom * containing the function name */
		    }
			*code = g_strconcat (*code, " ){}", NULL);
		    free(params);

//			node->pn_u.func.flags = TCF_COMPILING;
			break;
		case PN_NAME:
			printf ("%sNAME\n", pref);
			JSAtom * atom = node->pn_atom;
			*code = js_AtomToPrintableString(context, node->pn_atom);			
			break;
		case PN_NULLARY:
			printf ("%sNULL\n", pref);
			switch ((JSTokenType)node->pn_type)
			{
				case TOK_STRING:
					*code = g_strconcat ("\"", js_AtomToPrintableString(context, node->pn_atom), "\"", NULL);			
					break;
				case TOK_NUMBER:
					*code = g_new (char, 100);
					sprintf (*code, "%lf", node->pn_u.dval);
					break;
			}
			break;
		case PN_TERNARY:
			printf ("%sTERNARY\n", pref);
			print (node->pn_u.ternary.kid1, pr, &in);
			print (node->pn_u.ternary.kid2, pr, &in);			
			print (node->pn_u.ternary.kid3, pr, &in);			
			break;
		
	}
	g_free (pr);
	return node;
}

JSTrapStatus TrapHandler(JSContext *cx, JSScript *script,
                                  jsbytecode *pc, jsval *rval, void *closure)
{
	int i, k;  
	char str[100];
	scanf ("%s", &str);
	if (strcmp (str, "print") == 0)
	{
		JSIdArray  *oida = JS_Enumerate (cx, JS_GetGlobalObject(cx));
		for (k = 0; k < oida->length; k++)
		{
			printDesc (cx, JS_GetGlobalObject(cx), oida->vector[k], "\t");
		}
	}
	if (strcmp (str, "break") == 0)
	{
		return JSTRAP_RETURN;
	}
	return JSTRAP_CONTINUE;
}


int main(int argc, char **argv)
{
	JSRuntime *runtime = JS_NewRuntime (GC_MAX_MEM);
	JSObject  *global;

	if (argc != 2)
	{
		return 0;
	}
	gchar* filename = g_strdup (argv[1]);

	if (runtime == NULL)
	{
		g_error ("Can not create JSRuntime");
	}
	context = JS_NewContext (runtime, JS_STACK_SIZE);
	if (context == NULL)
	{
		g_error ("Can not create JSContext");
	}
	global = JS_NewObject(context, &global_class, NULL, NULL);
	if (global == NULL)
	{
		g_error ("Can not create Global object");
	}
	if (!JS_InitStandardClasses(context, global))
	{
		g_error ("Can not init standart classes");
	}

	JSTokenStream * stream = js_NewFileTokenStream(context, filename, stdin);
	gchar *code;
	JSParseNode *node = js_ParseTokenStream(context, global/*?????????*/, stream);
	print (node, "|", &code);
	if (code)
	{
		puts (code);
	}
	if (TRUE)
	{
		JSScript *script;
		JSObject *scriptObj;
		jsval result;
		jsint k;
		puts ("Local");
           

		script = JS_CompileFile(context, global, filename);
		if (!script)
		{
			g_error ("Can not compile script");
		}
		JS_SetTrap(context, script, JS_LineNumberToPC(context, script, 26),TrapHandler, NULL);


		JS_ExecuteScript(context, JS_GetGlobalObject(context), script, &result);

	}

	if (FALSE)
	{
		puts ("Global");
		JSIdArray *ida = NULL;
		jsint i;
		ida = JS_EnumerateResolvedStandardClasses (context, global, NULL);
		if (ida == NULL)
		{
			g_error ("Can not enumerate Global object");
		}
	
		for (i = 0; i < ida->length; i++)
		{
			printf ("%i ",i);
			printDesc (context, global, ida->vector[i], "\t");
		}
	}
	return 0;
}
